import 'dart:math' as math;

import 'package:vector_math/vector_math.dart';
import 'package:vector_math_test/src/test_utils.dart';

import '../common/test_page.dart';

class Obb3TestPage extends TestPage{
  Obb3TestPage(super.title) {
    group('Obb3', () {
      test('Obb3().copyCorner(cornerIndex, corner)', testCorners);
      test('Obb3().translate(Vector3 offset)', testTranslate);
      test('Obb3().rotate(Matrix4 t)', testRotate);
      test('Obb3().transform(Matrix4 t)', testTransform);
      test('Obb3().closestPointTo(Vector3 p, Vector3 q)', testClosestPointTo);
      test('Obb3().intersectsWithObb3(Obb3 other, [double epsilon = 1e-3])', testIntersectionObb3);
      test('Obb3().intersectsWithTriangle(Triangle other, {IntersectionResult? result})', testIntersectionTriangle);
      test('Obb3().intersectsWithVector3(Vector3 other)', testIntersectionVector3);
    });
  }

  void testCorners() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(5.0, 5.0, 5.0);
    final corner = Vector3.zero();

    a.copyCorner(0, corner);
    absoluteTest(corner, Vector3(-5.0, -5.0, -5.0));

    a.copyCorner(1, corner);
    absoluteTest(corner, Vector3(-5.0, -5.0, 5.0));

    a.copyCorner(2, corner);
    absoluteTest(corner, Vector3(-5.0, 5.0, -5.0));

    a.copyCorner(3, corner);
    absoluteTest(corner, Vector3(-5.0, 5.0, 5.0));

    a.copyCorner(4, corner);
    absoluteTest(corner, Vector3(5.0, -5.0, -5.0));

    a.copyCorner(5, corner);
    absoluteTest(corner, Vector3(5.0, -5.0, 5.0));

    a.copyCorner(6, corner);
    absoluteTest(corner, Vector3(5.0, 5.0, -5.0));

    a.copyCorner(7, corner);
    absoluteTest(corner, Vector3(5.0, 5.0, 5.0));
  }

  void testTranslate() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(5.0, 5.0, 5.0);
    final corner = Vector3.zero();

    a.translate(Vector3(-1.0, 2.0, 3.0));

    a.copyCorner(0, corner);
    absoluteTest(corner, Vector3(-5.0 - 1.0, -5.0 + 2.0, -5.0 + 3.0));

    a.copyCorner(1, corner);
    absoluteTest(corner, Vector3(-5.0 - 1.0, -5.0 + 2.0, 5.0 + 3.0));

    a.copyCorner(2, corner);
    absoluteTest(corner, Vector3(-5.0 - 1.0, 5.0 + 2.0, -5.0 + 3.0));

    a.copyCorner(3, corner);
    absoluteTest(corner, Vector3(-5.0 - 1.0, 5.0 + 2.0, 5.0 + 3.0));

    a.copyCorner(4, corner);
    absoluteTest(corner, Vector3(5.0 - 1.0, -5.0 + 2.0, -5.0 + 3.0));

    a.copyCorner(5, corner);
    absoluteTest(corner, Vector3(5.0 - 1.0, -5.0 + 2.0, 5.0 + 3.0));

    a.copyCorner(6, corner);
    absoluteTest(corner, Vector3(5.0 - 1.0, 5.0 + 2.0, -5.0 + 3.0));

    a.copyCorner(7, corner);
    absoluteTest(corner, Vector3(5.0 - 1.0, 5.0 + 2.0, 5.0 + 3.0));
  }

  void testRotate() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(5.0, 5.0, 5.0);
    final corner = Vector3.zero();
    final matrix = Matrix3.rotationY(radians(45.0));

    a.rotate(matrix);

    a.copyCorner(0, corner);
    absoluteTest(corner, Vector3(0.0, -5.0, -7.071067810058594));

    a.copyCorner(1, corner);
    absoluteTest(corner, Vector3(-7.071067810058594, -5.0, 0.0));

    a.copyCorner(2, corner);
    absoluteTest(corner, Vector3(0.0, 5.0, -7.071067810058594));

    a.copyCorner(3, corner);
    absoluteTest(corner, Vector3(-7.071067810058594, 5.0, 0.0));

    a.copyCorner(4, corner);
    absoluteTest(corner, Vector3(7.071067810058594, -5.0, 0.0));

    a.copyCorner(5, corner);
    absoluteTest(corner, Vector3(0.0, -5.0, 7.071067810058594));

    a.copyCorner(6, corner);
    absoluteTest(corner, Vector3(7.071067810058594, 5.0, 0.0));

    a.copyCorner(7, corner);
    absoluteTest(corner, Vector3(0.0, 5.0, 7.071067810058594));
  }

  void testTransform() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(5.0, 5.0, 5.0);
    final corner = Vector3.zero();
    final matrix = Matrix4.diagonal3Values(3.0, 3.0, 3.0);

    a.transform(matrix);

    a.copyCorner(0, corner);
    absoluteTest(corner, Vector3(-15.0, -15.0, -15.0));

    a.copyCorner(1, corner);
    absoluteTest(corner, Vector3(-15.0, -15.0, 15.0));

    a.copyCorner(2, corner);
    absoluteTest(corner, Vector3(-15.0, 15.0, -15.0));

    a.copyCorner(3, corner);
    absoluteTest(corner, Vector3(-15.0, 15.0, 15.0));

    a.copyCorner(4, corner);
    absoluteTest(corner, Vector3(15.0, -15.0, -15.0));

    a.copyCorner(5, corner);
    absoluteTest(corner, Vector3(15.0, -15.0, 15.0));

    a.copyCorner(6, corner);
    absoluteTest(corner, Vector3(15.0, 15.0, -15.0));

    a.copyCorner(7, corner);
    absoluteTest(corner, Vector3(15.0, 15.0, 15.0));
  }

  void testClosestPointTo() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(2.0, 2.0, 2.0);
    final b = Vector3(3.0, 3.0, 3.0);
    final c = Vector3(3.0, 3.0, -3.0);
    final closestPoint = Vector3.zero();

    a.closestPointTo(b, closestPoint);

    absoluteTest(closestPoint, Vector3(2.0, 2.0, 2.0));

    a.closestPointTo(c, closestPoint);

    absoluteTest(closestPoint, Vector3(2.0, 2.0, -2.0));

    a.rotate(Matrix3.rotationZ(radians(45.0)));

    a.closestPointTo(b, closestPoint);

    absoluteTest(closestPoint, Vector3(math.sqrt2, math.sqrt2, 2.0));

    a.closestPointTo(c, closestPoint);

    absoluteTest(closestPoint, Vector3(math.sqrt2, math.sqrt2, -2.0));
  }

  void testIntersectionObb3() {
    final a = Obb3()
      ..center.setValues(0.0, 0.0, 0.0)
      ..halfExtents.setValues(2.0, 2.0, 2.0);

    final b = Obb3()
      ..center.setValues(3.0, 0.0, 0.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final c = Obb3()
      ..center.setValues(0.0, 3.0, 0.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final d = Obb3()
      ..center.setValues(0.0, 0.0, 3.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final e = Obb3()
      ..center.setValues(-3.0, 0.0, 0.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final f = Obb3()
      ..center.setValues(0.0, -3.0, 0.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final g = Obb3()
      ..center.setValues(0.0, 0.0, -3.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final u = Obb3()
      ..center.setValues(1.0, 1.0, 1.0)
      ..halfExtents.setValues(0.5, 0.5, 0.5);

    final v = Obb3()
      ..center.setValues(10.0, 10.0, -10.0)
      ..halfExtents.setValues(2.0, 2.0, 2.0);

    final w = Obb3()
      ..center.setValues(10.0, 0.0, 0.0)
      ..halfExtents.setValues(1.0, 1.0, 1.0);

    // a - b
    expect(a.intersectsWithObb3(b), false);

    b.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(b), true);

    b.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(b), true);

    // a - c
    expect(a.intersectsWithObb3(c), false);

    c.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(c), true);

    c.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(c), true);

    // a - d
    expect(a.intersectsWithObb3(d), false);

    d.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(d), true);

    d.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(d), true);

    // a - e
    expect(a.intersectsWithObb3(e), false);

    e.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(e), true);

    e.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(e), true);

    // a - f
    expect(a.intersectsWithObb3(f), false);

    f.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(f), true);

    f.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(f), true);

    // a - g
    expect(a.intersectsWithObb3(g), false);

    g.halfExtents.scale(2.0);

    expect(a.intersectsWithObb3(g), true);

    g.rotate(Matrix3.rotationZ(radians(45.0)));

    expect(a.intersectsWithObb3(g), true);

    // u
    expect(a.intersectsWithObb3(u),true);

    expect(b.intersectsWithObb3(u), false);

    u.halfExtents.scale(10.0);

    expect(b.intersectsWithObb3(u), true);

    // v
    expect(a.intersectsWithObb3(v), false);

    expect(b.intersectsWithObb3(v), false);

    // w
    expect(a.intersectsWithObb3(w), false);

    w.rotate(Matrix3.rotationZ(radians(22.0)));

    expect(a.intersectsWithObb3(w), false);

    expect(b.intersectsWithObb3(w), false);
  }

  void testIntersectionVector3() {
    //final parent = new Aabb3.minMax(_v(1.0,1.0,1.0), _v(8.0,8.0,8.0));
    final parent = Obb3()
      ..center.setValues(4.5, 4.5, 4.5)
      ..halfExtents.setValues(3.5, 3.5, 3.5);
    final child = $v3(7.0, 7.0, 7.0);
    final cutting = $v3(1.0, 2.0, 1.0);
    final outside1 = $v3(-10.0, 10.0, 10.0);
    final outside2 = $v3(4.5, 4.5, 9.0);

    expect(parent.intersectsWithVector3(child), true);
    expect(parent.intersectsWithVector3(cutting), true);
    expect(parent.intersectsWithVector3(outside1), false);
    expect(parent.intersectsWithVector3(outside2), false);

    final rotationX = Matrix3.rotationX(radians(45.0));
    parent.rotate(rotationX);

    expect(parent.intersectsWithVector3(child), false);
    expect(parent.intersectsWithVector3(cutting), false);
    expect(parent.intersectsWithVector3(outside1), false);
    expect(parent.intersectsWithVector3(outside2), true);
  }

  void testIntersectionTriangle() {
    final parent = Obb3();
    parent.center.setValues(4.5, 4.5, 4.5);
    parent.halfExtents.setValues(3.5, 3.5, 3.5);
    final child = Triangle.points(
        $v3(2.0, 2.0, 2.0), $v3(3.0, 3.0, 3.0), $v3(4.0, 4.0, 4.0));
    final edge = Triangle.points(
        $v3(1.0, 1.0, 1.0), $v3(3.0, 3.0, 3.0), $v3(4.0, 4.0, 4.0));
    final cutting = Triangle.points(
        $v3(2.0, 2.0, 2.0), $v3(3.0, 3.0, 3.0), $v3(14.0, 14.0, 14.0));
    final outside = Triangle.points(
        $v3(0.0, 0.0, 0.0), $v3(-3.0, -3.0, -3.0), $v3(-4.0, -4.0, -4.0));
    final parallel0 = Triangle.points(
        $v3(1.0, 0.0, 1.0), $v3(1.0, 10.0, 1.0), $v3(1.0, 0.0, 10.0));
    final parallel1 = Triangle.points(
        $v3(1.0, 4.5, 0.0), $v3(1.0, -1.0, 9.0), $v3(1.0, 10.0, 9.0));
    final parallel2 = Triangle.points(
        $v3(1.0, 10.0, 9.0), $v3(1.0, -1.0, 9.0), $v3(1.0, 4.5, 0.0));

    expect(parent.intersectsWithTriangle(child), true);
    expect(parent.intersectsWithTriangle(edge), true);
    expect(parent.intersectsWithTriangle(cutting), true);
    expect(parent.intersectsWithTriangle(outside), false);
    expect(parent.intersectsWithTriangle(parallel0), true);
    expect(parent.intersectsWithTriangle(parallel1), true);
    expect(parent.intersectsWithTriangle(parallel2), true);

    final rotationX = Matrix3.rotationX(radians(0.01));
    parent.rotate(rotationX);

    expect(parent.intersectsWithTriangle(child), true);
    expect(parent.intersectsWithTriangle(edge), true);
    expect(parent.intersectsWithTriangle(cutting), true);
    expect(parent.intersectsWithTriangle(outside), false);
    expect(parent.intersectsWithTriangle(parallel0), true);
    expect(parent.intersectsWithTriangle(parallel1), true);
    expect(parent.intersectsWithTriangle(parallel2), true);

    final rotationY = Matrix3.rotationY(radians(45.0));
    parent.rotate(rotationY);

    expect(parent.intersectsWithTriangle(child), true);
    expect(parent.intersectsWithTriangle(edge), true);
    expect(parent.intersectsWithTriangle(cutting), true);
    expect(parent.intersectsWithTriangle(outside), false);
    expect(parent.intersectsWithTriangle(parallel0), true);
    expect(parent.intersectsWithTriangle(parallel1), true);
    expect(parent.intersectsWithTriangle(parallel2), true);

    final rotationZ = Matrix3.rotationZ(radians(45.0));
    parent.rotate(rotationZ);

    expect(parent.intersectsWithTriangle(child), true);
    expect(parent.intersectsWithTriangle(edge), true);
    expect(parent.intersectsWithTriangle(cutting), true);
    expect(parent.intersectsWithTriangle(outside), false);
    expect(parent.intersectsWithTriangle(parallel0), true);
    expect(parent.intersectsWithTriangle(parallel1), true);
    expect(parent.intersectsWithTriangle(parallel2), true);

    final obb = Obb3.centerExtentsAxes(
        $v3(21.0, -36.400001525878906, 2.799999952316284),
        $v3(0.25, 0.15000000596046448, 0.25),
        $v3(0.0, 1.0, 0.0),
        $v3(-1.0, 0.0, 0.0),
        $v3(0.0, 0.0, 1.0));
    final triangle = Triangle.points(
        $v3(20.5, -36.5, 3.5), $v3(21.5, -36.5, 2.5), $v3(20.5, -36.5, 2.5));

    expect(obb.intersectsWithTriangle(triangle), true);

    final obb2 = Obb3.centerExtentsAxes(
        $v3(25.15829086303711, -36.27009201049805, 3.0299079418182373),
        $v3(0.25, 0.15000000596046448, 0.25),
        $v3(-0.7071067690849304, 0.7071067690849304, 0.0),
        $v3(-0.7071067690849304, -0.7071067690849304, 0.0),
        $v3(0.0, 0.0, 1.0));
    final triangle2 = Triangle.points(
        $v3(25.5, -36.5, 2.5), $v3(25.5, -35.5, 3.5), $v3(24.5, -36.5, 2.5));
    final triangle2_1 = Triangle.points($v3(24.5, -36.5, 2.5),
        $v3(25.5, -35.5, 3.5), $v3(25.5, -36.5, 2.5)); // reverse normal direction

    expect(obb2.intersectsWithTriangle(triangle2), true);
    expect(obb2.intersectsWithTriangle(triangle2_1), true);

    final obb3 = Obb3.centerExtentsAxes(
        $v3(20.937196731567383, -37.599998474121094, 2.799999952316284),
        $v3(0.25, 0.15000000596046448, 0.25),
        $v3(0.0, -1.0, 0.0),
        $v3(1.0, 0.0, 0.0),
        $v3(0.0, 0.0, 1.0));
    final triangle3 = Triangle.points(
        $v3(20.5, -37.5, 3.5), $v3(20.5, -37.5, 2.5), $v3(21.5, -37.5, 2.5));
    final triangle3_1 = Triangle.points($v3(21.5, -37.5, 2.5),
        $v3(20.5, -37.5, 2.5), $v3(20.5, -37.5, 3.5)); // reverse normal direction

    expect(obb3.intersectsWithTriangle(triangle3), true);
    expect(obb3.intersectsWithTriangle(triangle3_1), true);

    final obb4 = Obb3.centerExtentsAxes(
        $v3(19.242143630981445, -39.20925521850586, 2.549999952316284),
        $v3(0.25, 0.15000000596046448, 0.25),
        $v3(0.0, 1.0, 0.0),
        $v3(-1.0, 0.0, 0.0),
        $v3(0.0, 0.0, 1.0));
    final triangle4 = Triangle.points(
        $v3(18.5, -39.5, 2.5), $v3(19.5, -39.5, 2.5), $v3(19.5, -38.5, 2.5));
    final triangle4_1 = Triangle.points($v3(19.5, -38.5, 2.5),
        $v3(19.5, -39.5, 2.5), $v3(18.5, -39.5, 2.5)); // reverse normal direction
    final triangle4_2 = Triangle.points(
        $v3(18.5, -39.5, 2.5), $v3(19.5, -38.5, 2.5), $v3(18.5, -38.5, 2.5));
    final triangle4_3 = Triangle.points($v3(18.5, -38.5, 2.5),
        $v3(19.5, -38.5, 2.5), $v3(18.5, -39.5, 2.5)); // reverse normal direction

    expect(obb4.intersectsWithTriangle(triangle4), true);
    expect(obb4.intersectsWithTriangle(triangle4_1), true);
    expect(obb4.intersectsWithTriangle(triangle4_2), false);
    expect(obb4.intersectsWithTriangle(triangle4_3), false);
  }

}